// Copyright (c) 2018 Baidu.com, Inc. All Rights Reserved
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Authors: Zhangyi Chen(chenzhangyi01@baidu.com)

#include <map>                  // std::map
#include <turbo/flags/flag.h>
#include <turbo/flags/app.h>
#include <krpc/utility/string_printf.h>
#include <kraft/cli.h>          // raft::cli::*

TURBO_FLAG(int32_t, timeout_ms, -1, "Timeout (in milliseconds) of the operation");
TURBO_FLAG(int32_t, max_retry, 3, "Max retry times of each operation");
TURBO_FLAG(std::string, conf, "", "Current configuration of the raft group");
TURBO_FLAG(std::string, peer, "", "Id of the operating peer");
TURBO_FLAG(std::string, new_peers, "", "Peers that the group is going to consists of");
TURBO_FLAG(std::string, group, "", "Id of the raft group");

namespace kraft {
    namespace cli {

#define CHECK_FLAG(flagname)                                            \
    do {                                                                \
        if (turbo::get_flag(FLAGS_##flagname).empty()) {                             \
            LOG(ERROR) << __FUNCTION__ << " requires --" # flagname ;   \
            return -1;                                                  \
        }                                                               \
    } while (0);


        int cmd_add_peer() {
            CHECK_FLAG(conf);
            CHECK_FLAG(peer);
            CHECK_FLAG(group);
            Configuration conf;
            if (conf.parse_from(turbo::get_flag(FLAGS_conf)) != 0) {
                LOG(ERROR) << "Fail to parse --conf=`" << turbo::get_flag(FLAGS_conf) << '\'';
                return -1;
            }
            PeerId new_peer;
            if (new_peer.parse(turbo::get_flag(FLAGS_peer)) != 0) {
                LOG(ERROR) << "Fail to parse --peer=`" << turbo::get_flag(FLAGS_peer) << '\'';
                return -1;
            }
            CliOptions opt;
            opt.timeout_ms = turbo::get_flag(FLAGS_timeout_ms);
            opt.max_retry = turbo::get_flag(FLAGS_max_retry);
            kutil::Status st = add_peer(turbo::get_flag(FLAGS_group), conf, new_peer, opt);
            if (!st.ok()) {
                LOG(ERROR) << "Fail to add_peer : " << st;
                return -1;
            }
            return 0;
        }

        int cmd_remove_peer() {
            CHECK_FLAG(conf);
            CHECK_FLAG(peer);
            CHECK_FLAG(group);
            Configuration conf;
            if (conf.parse_from(turbo::get_flag(FLAGS_conf)) != 0) {
                LOG(ERROR) << "Fail to parse --conf=`" << turbo::get_flag(FLAGS_conf) << '\'';
                return -1;
            }
            PeerId removing_peer;
            if (removing_peer.parse(turbo::get_flag(FLAGS_peer)) != 0) {
                LOG(ERROR) << "Fail to parse --peer=`" << turbo::get_flag(FLAGS_peer) << '\'';
                return -1;
            }
            CliOptions opt;
            opt.timeout_ms = turbo::get_flag(FLAGS_timeout_ms);
            opt.max_retry = turbo::get_flag(FLAGS_max_retry);
            kutil::Status st = remove_peer(turbo::get_flag(FLAGS_group), conf, removing_peer, opt);
            if (!st.ok()) {
                LOG(ERROR) << "Fail to remove_peer : " << st;
                return -1;
            }
            return 0;
        }

        int cmd_change_peers() {
            CHECK_FLAG(new_peers);
            CHECK_FLAG(conf);
            CHECK_FLAG(group);
            Configuration new_peers;
            if (new_peers.parse_from(turbo::get_flag(FLAGS_new_peers)) != 0) {
                LOG(ERROR) << "Fail to parse --new_peers=`" << turbo::get_flag(FLAGS_new_peers) << '\'';
                return -1;
            }
            Configuration conf;
            if (conf.parse_from(turbo::get_flag(FLAGS_conf)) != 0) {
                LOG(ERROR) << "Fail to parse --conf=`" << turbo::get_flag(FLAGS_conf) << '\'';
                return -1;
            }
            CliOptions opt;
            opt.timeout_ms = turbo::get_flag(FLAGS_timeout_ms);
            opt.max_retry = turbo::get_flag(FLAGS_max_retry);
            kutil::Status st = change_peers(turbo::get_flag(FLAGS_group), conf, new_peers, opt);
            if (!st.ok()) {
                LOG(ERROR) << "Fail to change_peers : " << st;
                return -1;
            }
            return 0;
        }

        int cmd_reset_peer() {
            CHECK_FLAG(new_peers);
            CHECK_FLAG(peer);
            CHECK_FLAG(group);
            Configuration new_peers;
            if (new_peers.parse_from(turbo::get_flag(FLAGS_new_peers)) != 0) {
                LOG(ERROR) << "Fail to parse --new_peers=`" << turbo::get_flag(FLAGS_new_peers) << '\'';
                return -1;
            }
            PeerId target_peer;
            if (target_peer.parse(turbo::get_flag(FLAGS_peer)) != 0) {
                LOG(ERROR) << "Fail to parse --peer=`" << turbo::get_flag(FLAGS_peer) << '\'';
                return -1;
            }
            CliOptions opt;
            opt.timeout_ms = turbo::get_flag(FLAGS_timeout_ms);
            opt.max_retry = turbo::get_flag(FLAGS_max_retry);
            kutil::Status st = reset_peer(turbo::get_flag(FLAGS_group), target_peer, new_peers, opt);
            if (!st.ok()) {
                LOG(ERROR) << "Fail to reset_peer : " << st;
                return -1;
            }
            return 0;
        }

        int cmd_snapshot() {
            CHECK_FLAG(peer);
            CHECK_FLAG(group);
            PeerId target_peer;
            if (target_peer.parse(turbo::get_flag(FLAGS_peer)) != 0) {
                LOG(ERROR) << "Fail to parse --peer=`" << turbo::get_flag(FLAGS_peer) << '\'';
                return -1;
            }
            CliOptions opt;
            opt.timeout_ms = turbo::get_flag(FLAGS_timeout_ms);
            opt.max_retry = turbo::get_flag(FLAGS_max_retry);
            kutil::Status st = snapshot(turbo::get_flag(FLAGS_group), target_peer, opt);
            if (!st.ok()) {
                LOG(ERROR) << "Fail to make snapshot : " << st;
                return -1;
            }
            return 0;
        }

        int cmd_transfer_leader() {
            CHECK_FLAG(conf);
            CHECK_FLAG(peer);
            CHECK_FLAG(group);
            Configuration conf;
            if (conf.parse_from(turbo::get_flag(FLAGS_conf)) != 0) {
                LOG(ERROR) << "Fail to parse --conf=`" << turbo::get_flag(FLAGS_conf) << '\'';
                return -1;
            }
            PeerId target_peer;
            if (target_peer.parse(turbo::get_flag(FLAGS_peer)) != 0) {
                LOG(ERROR) << "Fail to parse --peer=`" << turbo::get_flag(FLAGS_peer) << '\'';
                return -1;
            }
            CliOptions opt;
            opt.timeout_ms = turbo::get_flag(FLAGS_timeout_ms);
            opt.max_retry = turbo::get_flag(FLAGS_max_retry);
            kutil::Status st = transfer_leader(turbo::get_flag(FLAGS_group), conf, target_peer, opt);
            if (!st.ok()) {
                LOG(ERROR) << "Fail to transfer_leader: " << st;
                return -1;
            }
            return 0;
        }

    }  // namespace cli
}  // namespace raft

int main(int argc, char *argv[]) {
    const char *proc_name = strrchr(argv[0], '/');
    if (proc_name == NULL) {
        proc_name = argv[0];
    } else {
        ++proc_name;
    }
    turbo::cli::App app;
    app.add_option("--group", FLAGS_group, "raft group")->required();
    app.add_option("--peer", FLAGS_conf, "raft peer")->required();
    app.add_option("--new-peer", FLAGS_conf, "raft new peer")->required();
    app.add_option("--conf", FLAGS_conf, "raft conf")->required();
    app.add_subcommand("add_peer", "add peer to raft group")->callback([]() {
        kraft::cli::cmd_add_peer();
    });

    app.add_subcommand("remove_peer", "remove peer to raft group")->callback([]() {
        kraft::cli::cmd_remove_peer();
    });

    app.add_subcommand("change_peers", "change peer to raft group")->callback([]() {
        kraft::cli::cmd_change_peers();
    });

    app.add_subcommand("reset_peer", "reset peer to raft group")->callback([]() {
        kraft::cli::cmd_reset_peer();
    });

    app.add_subcommand("snapshot", "snapshot to raft group")->callback([]() {
        kraft::cli::cmd_snapshot();
    });

    app.add_subcommand("transfer_leader", "transfer leader to raft group")->callback([]() {
        kraft::cli::cmd_transfer_leader();
    });
    app.require_subcommand(1);
    TURBO_APP_PARSE(app, argc, argv);
    return 0;
}
